#include "ControllerFallingMode.h"
#include <thread>
#include <Windows.h>

ControllerFallingMode::ControllerFallingMode(string noteFilePath, float hispeed, int filetype)
{
	type_file = filetype;
	//reload notemgr
	pNotemgr = new NoteManager(noteFilePath, NoteManager::FILETYPE(type_file));

	baseSpeed = hispeed;
	trackCount = pNotemgr->trackCount;
	notesInTrack = new vector<NoteObj>[trackCount];
	trackNoteIndex = new unsigned int[trackCount];
	thread tLoadSnd(&ControllerFallingMode::loadSounds, this);
	tLoadSnd.detach();
	trackStatus = new TrackStatus[trackCount];
	trackLastStatus = new TrackStatus[trackCount];
	for (int i = 0; i < trackCount; i++)
	{
		trackStatus[i] = { false,false,0 };
		trackLastStatus[i] = { false,false,0 };
		totalNotesCount += pNotemgr->trackNotes[i].size();
		trackNoteIndex[i] = 0;
	}
	pConfigKeys->setActiveKeySet(trackCount);
	type_key = pConfig->getIntValue("gameplay", "gamestyle");
	if (type_key == 0) type_key = filetype;
	pConfigKeys->setActiveType(type_key);
}


void ControllerFallingMode::setSceneObject(Node* layerHUD, Node* panelFrame)
{
	pPanelFrame = (UIFallingModePanel*)panelFrame;
	pNoteField = pPanelFrame->getChildByName("panelClip")->getChildByName("panelNoteField");
	pNoteField->setScaleY(baseSpeed);
	pKeyButtons = new Node*[trackCount];
	pTrackLights = new Node*[trackCount];
	if (type_key == 2)
	{
		trackWidthS = 1.618f * pNoteField->getContentSize().width / pNotemgr->trackCount;
		trackWidth = (pNoteField->getContentSize().width - trackWidthS) / (pNotemgr->trackCount - 1);
	}
	else
	{
		trackWidth = pNoteField->getContentSize().width / pNotemgr->trackCount;
		trackWidthS = trackWidth;
	}
	pPanelFrame->setTrackWidth(trackWidth, trackWidthS);
	pPanelFrame->createKeys();
	char name[6];
	for (int i = 0; i < trackCount; i++)
	{
		sprintf_s(name, 6, "key%d", i);
		pKeyButtons[i] = pPanelFrame->getChildByName(name);
		sprintf_s(name, 6, "tkl%d", i);
		pTrackLights[i] = pPanelFrame->getChildByName(name);
	}
	noteFieldHeight = pNoteField->getContentSize().height;
	pHud = layerHUD;
	pHitJudgment = UIFallingModeHitJudgment::create(Size(pPanelFrame->getContentSize().width*.8f, pPanelFrame->getContentSize().width*.125f));
	pHitJudgment->setPosition(pPanelFrame->getContentSize().width / 2, pPanelFrame->getContentSize().height * 1 / 3);
	pPanelFrame->addChild(pHitJudgment);
	if (!isSoundsLoaded)
	{
		pHitJudgment->setInfo(UIFallingModeHitJudgment::LOADING, "");
		thread([&]
		{
			while (!isSoundsLoaded) { this_thread::sleep_for(chrono::milliseconds(200)); }
			pHitJudgment->setInfo(UIFallingModeHitJudgment::DREAM, "0");
		}
		).detach();
	}

	//pLayerAbility = LayerColor::create(Color4B(0, 0, 0, 150), pPanelFrame->getContentSize().width, frameSize.height / 3);
	//pLayerAbility->setPosition(pPanelFrame->getPosition().x + pPanelFrame->getContentSize().width,
	//						   frameSize.height*0.3);
	//pHud->addChild(pLayerAbility);
	//fontStyle->fontSize = 35;
	//pLabelAbility = Label::createWithTTF(*fontStyle, "");
	//pLabelAbility->enableGlow(Color4B::BLACK);
	//pLabelAbility->enableOutline(Color4B::BLACK, 2);
	//UI::setNodeAnch(pLabelAbility, ANCH_BL);
	//pLabelAbility->setPosition(0, 0);
	//pLayerAbility->addChild(pLabelAbility);
	//pLabelAbility->runAction(
	//	RepeatForever::create(
	//		Sequence::create(
	//			FadeTo::create(0, 100),
	//			DelayTime::create(0.02f),
	//			FadeTo::create(0, 235),
	//			DelayTime::create(0.02f),
	//			NULL
	//		)
	//	)
	//);

	//pLayerGraph = LayerColor::create(Color4B(0, 0, 0, 150), pPanelFrame->getContentSize().width, frameSize.height / 3);
	//pLayerGraph->setPosition(pPanelFrame->getPosition().x - pPanelFrame->getContentSize().width,
	//						 frameSize.height*0.3);
	//pHud->addChild(pLayerGraph);
	//graphRows = 17;
	//graphRowHeight = (frameSize.height / 3) / (graphRows + 2);
	//float graphTextPosX = pLayerGraph->getContentSize().width*0.75;
	//graphOffsetCount = new int[graphRows];
	//pOffsetGraph = new Node*[graphRows];
	//graphInterval = 2 * OFF_POOR / (graphRows - 2);
	//fontStyle->fontFilePath = FONTB;
	//fontStyle->fontSize = 18;
	//for (int i = -1; i < graphRows; i++)
	//{
	//	char rowText[16];
	//	if (i > 0 && i < graphRows - 1) sprintf_s<16>(rowText, "%+d~%+d", OFF_POOR - graphInterval*i, OFF_POOR - graphInterval*(i - 1));
	//	else if (i == 0) sprintf_s<16>(rowText, ">+%d", OFF_POOR);
	//	else if (i == graphRows - 1) sprintf_s<16>(rowText, "<-%d", OFF_POOR);
	//	else if (i == -1) sprintf_s<16>(rowText, "Offset Graph");
	//	auto pLabelGraph = Label::createWithTTF(*fontStyle, rowText);
	//	pLabelGraph->enableOutline(Color4B::BLACK, 1);
	//	pLabelGraph->ignoreAnchorPointForPosition(false);
	//	if (i >= 0)
	//	{
	//		pLabelGraph->setAnchorPoint(Point(0, 0));
	//		float c = 60 + 55 * sqrt(abs(i - graphRows / 2));
	//		pOffsetGraph[i] = LayerColor::create(Color4B(240, c <= 255 ? c : 255, 240, 255), 1, graphRowHeight);
	//		UI::setNodeAnch(pOffsetGraph[i], ANCH_BR);
	//		pOffsetGraph[i]->setPosition(Point(graphTextPosX, graphRowHeight*(i + 1)));
	//		pLayerGraph->addChild(pOffsetGraph[i]);
	//		graphOffsetCount[i] = 0;
	//	}
	//	else
	//	{
	//		pLabelGraph->setAnchorPoint(ANCH_BR);
	//	}
	//	pLabelGraph->setPosition(graphTextPosX, graphRowHeight*(i + 1));
	//	pLayerGraph->addChild(pLabelGraph);

	//}
	setTimeSegPara();
}

void ControllerFallingMode::updateController(bool includeAudio)
{
	if (isSoundsLoaded && !isThreadStart) startThreads();
	if (includeAudio) updateAudio();
	if (type_file == 1)
	{
		setCurTimeHighPrecision();
		setTopTime();
	}
	//else setCurTimeHighPrecisionBMS();
	if (type_file == 2)
	{
		deltaTime = deltaTimeBMS;
		deltaTimeBMS = 0;
	}
	changeHispeed();
	moveNotesInField();
	drawNewNotes();
	autoPlay();
	checkNoteHit();
	removeOldNotes();
	updateKeyEffect();
	showHitEffect();
	showHitLevel();
	//updateScore();
	//updateOffsetGraph();
}

int ControllerFallingMode::getTrackCount()
{
	return trackCount;
}

int ControllerFallingMode::getTimeSeg(int timePoint)
{
	int index = -1;
	for (int i = 0; i < (int)pNotemgr->timingSegs.size(); i++)
	{
		int linePoint = 0;
		if (i > 0)linePoint = pNotemgr->timingSegs[i].startTime;
		if (timePoint < linePoint)
		{
			index = i - 1;
			break;
		}
	}
	if (index >= 0) return index;
	else return pNotemgr->timingSegs.size() - 1;
}


void ControllerFallingMode::setTimeSegPara()
{
	pTimeSegPara = new TimeSegPara[pNotemgr->timingSegs.size()];
	for (int i = 0; i < (int)pNotemgr->timingSegs.size(); i++)
	{
		float mspb = pNotemgr->timingSegs[i].mspb;
		float speedmod = pNotemgr->timingSegs[i].speedModer;
		pTimeSegPara[i].startTime = i == 0 ? 0 : pNotemgr->timingSegs[i].startTime;
		pTimeSegPara[i].noteSpeedPerMs = speedmod*noteFieldHeight / (mspb * 4);
		pTimeSegPara[i].timeFlowSpeedPerGLP = 1 / pTimeSegPara[i].noteSpeedPerMs;
	}
}

void ControllerFallingMode::updateAudio()
{
	switch (type_file)
	{
		case 1:
			if (curTime >= 0 && !bgmStarted && isSoundsLoaded)
			{
				pAudioSystem->update();
				pAudioSystem->playLoadedBGM(VOLUMEMUSIC);
				bgmStarted = true;
			}
			if (isPaused != bgmIsPausedPrev)
			{
				if (isPaused) pAudioSystem->pauseBGM();
				else pAudioSystem->resumeBGM();
				bgmIsPausedPrev = isPaused;
			}
			else
			{
				pAudioSystem->update();
			}
			break;

		case 2:
			//pAudioSystem->update();
			break;

		default:
			break;
	}
}

void ControllerFallingMode::setCurTimeHighPrecision()
{
	if (!isThreadStart) return;
	unsigned int bgmTime = pAudioSystem->getBgmPosition();
	hTimer->setcheckpoint();
	deltaTime = hTimer->getElapsedReset();
	if (curTime >= 0 && bgmTime > 0 && !isPaused)
	{
		curTime += deltaTime*deltaTimeMod;
		totalFrames++;
		if (bgmTime != prevBgmTime)
		{
			prevBgmTime = bgmTime;
			prevGameTime = curTime;
			avOffset = bgmTime - curTime;
			audioBufferFrames++;
			audioBufferCount++;
			avOffsetPerFrame = avOffset / (audioBufferFrames / audioBufferCount);
			if (deltaTime != 0)
			{
				if (avOffset >= 0) deltaTimeMod = (deltaTime + avOffsetPerFrame) / deltaTime;
				else deltaTimeMod = deltaTime / (deltaTime - avOffsetPerFrame);
			}
		}
		else audioBufferFrames++;

		if (timeSegIndex < ((int)pNotemgr->timingSegs.size() - 1)
			&& (curTime >= pNotemgr->timingSegs[timeSegIndex + 1].startTime))
		{
			timeSegIndex++;
		}
	}
	else if (curTime < 0 && !isPaused)
	{
		curTime += deltaTime;
	}
}

void ControllerFallingMode::setCurTimeHighPrecisionBMS()
{
	while (!exit)
	{
		if (isSoundsLoaded)
		{
			if (!isPaused)
			{
				auto newtime = hTimer->getElapsed();
				deltaTimeBMS += newtime - curTime;
				curTime = newtime;
				//deltaTime = hTimer->getElapsedReset();
				//curTime += deltaTime;
				if (timeSegIndex < ((int)pNotemgr->timingSegs.size() - 1)
					&& (curTime >= pNotemgr->timingSegs[timeSegIndex + 1].startTime))
				{
					timeSegIndex++;
				}
				setTopTime();
			}
		}
		else
		{
			hTimer->reset();
		}
		this_thread::sleep_for(chrono::milliseconds(1));
	}
}

float ControllerFallingMode::getGameTimeHighPrecision()
{
	//LARGE_INTEGER tick, freq;
	//QueryPerformanceCounter(&tick);
	//QueryPerformanceFrequency(&freq);
	//return curTime + 1000 * (tick.QuadPart - cpuTick.QuadPart) / (float)freq.QuadPart;
	return curTime + hTimer->getElapsed();
}

void ControllerFallingMode::setCurTime(float dt)
{
	unsigned int bgmTime = pAudioSystem->getBgmPosition();
	if (curTime >= 0 && bgmTime > 0)
	{
		deltaTime = Director::getInstance()->getDeltaTime() * 1000;
		float gameTime = curTime + deltaTime;
		if (abs(gameTime - bgmTime) > 50)
		{
			curTime = bgmTime;
		}
		else if (gameTime < (float)bgmTime - 10)
		{
			curTime = gameTime + deltaTime*0.15;
		}
		else if (gameTime > (float)bgmTime + 10)
		{
			curTime = gameTime - deltaTime*0.15;
		}
		else
		{
			curTime = gameTime;
		}
		if (timeSegIndex < ((int)pNotemgr->timingSegs.size() - 1) && (curTime >= pNotemgr->timingSegs[timeSegIndex + 1].startTime))
		{
			timeSegIndex++;
		}
	}
}


//void ControllerFallingMode::moveNotesInField()
//{
//	if (!bgmStarted && type_file == 1) return;
//	if (curTime < 0 && type_file == 2) return;
//	for (int i = 0; i < pNotemgr->trackCount; i++)
//	{
//		int noteCount = notesInTrack[i].size();
//		if ((noteCount > 0) && (deltaTime > 0))
//		{
//			for (Vector<NoteObj>::const_iterator iter = notesInTrack[i].cbegin(); iter != notesInTrack[i].cend(); iter++)
//			{
//				iter->obj->setPositionY(iter->obj->getPositionY() - pTimeSegPara[timeSegIndex].noteSpeedPerMs*deltaTime);
//				//iter->obj->setPositionY((iter->info.startTime - curTime)*pTimeSegPara[timeSegIndex].noteSpeedPerMs);
//				//iter->obj->setPositionY(getNoteOffsetY(iter->info.startTime));
//			}
//		}
//	}
//}

void ControllerFallingMode::moveNotesInField()
{
	if (!bgmStarted && type_file == 1) return;
	if (curTime < 0 && type_file == 2) return;
	Vec2 anch = pNoteField->getAnchorPoint();
	anch.y += pTimeSegPara[timeSegIndex].noteSpeedPerMs*deltaTime / pNoteField->getContentSize().height;
	pNoteField->setAnchorPoint(anch);
}

void ControllerFallingMode::setTopTime()
{
	float virtualTopTime;
	virtualTopTime = curTime + pTimeSegPara[timeSegIndex].timeFlowSpeedPerGLP*(noteFieldHeight / baseSpeed);
	int topTimeSegIndex = getTimeSeg(virtualTopTime);
	if (timeSegIndex == topTimeSegIndex)
	{
		topTime = virtualTopTime;
	}
	else
	{
		float offsetTime = curTime;
		float linePos = 0;
		int nearestSegIndex = timeSegIndex;
		for (int i = timeSegIndex; i < topTimeSegIndex; i++)
		{
			int nextLineTime = pTimeSegPara[i + 1].startTime;
			float nextLinePos = (nextLineTime - offsetTime)*pTimeSegPara[i].timeFlowSpeedPerGLP;
			if ((nextLinePos + linePos) < noteFieldHeight)
			{
				offsetTime = nextLineTime;
				linePos += nextLinePos;
			}
			nearestSegIndex = i;
		}
		offsetTime += ((noteFieldHeight - linePos) / baseSpeed) *pTimeSegPara[nearestSegIndex].timeFlowSpeedPerGLP;
		topTime = offsetTime;
	}

}

void ControllerFallingMode::drawNewNotes()
{
	for (int i = 0; i < trackCount; i++)
	{
		if (pNotemgr->trackNotes[i].size() == 0) continue;
		Color4B notecolor = Color4B::WHITE;
		switch (pConfigKeys->activeKeySetType)
		{
			case 1:
				notecolor = Color4B(255 - 100 * (i % 2), 255 - 100 * (i % 2), 255, 255);
				break;
			case 2:
				if (i == 0) notecolor = Color4B(255, 80, 80, 255);
				else notecolor = Color4B(155 + 100 * (i % 2), 155 + 100 * (i % 2), 255, 255);
				break;
			default:
				break;
		}
		for (; trackNoteIndex[i] < pNotemgr->trackNotes[i].size(); trackNoteIndex[i]++)
		{
			auto tracknote = pNotemgr->trackNotes[i];
			auto iter = tracknote.at(trackNoteIndex[i]);
			if (iter.startTime < (topTime + 1000))
			{
				NoteObj note;
				//float notePosY = getNoteOffsetY(iter.startTime) + pNoteField->getContentSize().height*pNoteField->getAnchorPoint().y;
				float notePosY = getNoteOffsetY(iter.startTime);
				note.obj = LayerColor::create(
					notecolor,
					(i == 0 ? trackWidthS : trackWidth) - 4,
					singleNoteHeight);
				note.info = iter;
				UI::setNodeAnch(note.obj, ANCH_BL);
				note.obj->setPosition((i == 0 ? 0 : trackWidthS + trackWidth*(i - 1)) + 1, notePosY);
				if (note.info.noteType == note.info.LONG) note.obj->setContentSize(Size((i == 0 ? trackWidthS : trackWidth) - 4, getNoteOffsetY(iter.endTime) - notePosY + singleNoteHeight / baseSpeed));
				else note.obj->setScaleY(1 / baseSpeed);
				pNoteField->addChild(note.obj);
				CCLOG("NOTE TIME:%d, POS_S:%.2f", note.info.startTime, note.obj->getPositionY()*baseSpeed);
				notesInTrack[i].push_back(note);
			}
			else break;
		}
	}
}

float ControllerFallingMode::getNoteOffsetY(int timePoint)
{
	int baseTime = curTime;
	int targetSegIndex = getTimeSeg(timePoint);
	float posy = 0;
	for (int i = timeSegIndex; i < targetSegIndex; i++)
	{
		int nextLineTime = pTimeSegPara[i + 1].startTime;
		posy += (nextLineTime - baseTime)*pTimeSegPara[i].noteSpeedPerMs;
		baseTime = nextLineTime;
	}
	posy += (timePoint - baseTime)*pTimeSegPara[targetSegIndex].noteSpeedPerMs;
	posy += pNoteField->getContentSize().height*pNoteField->getAnchorPoint().y;
	return posy;
}

void ControllerFallingMode::removeOldNotes()
{
	for (int i = 0; i < trackCount; i++)
	{
		if (notesInTrack[i].size() == 0) continue;
		vector<NoteObj>::iterator iter = notesInTrack[i].begin();
		if ((iter->info.noteType == iter->info.SINGLE) && (iter->hitting || ((curTime - iter->info.startTime) > OFF_BREAK)))
		{
			deleteNote(i, iter);
		}
		else if (
			(iter->info.noteType == iter->info.LONG) &&
			(
			((curTime - iter->info.endTime) > OFF_BREAK) ||
				(iter->hitting && (iter->info.endTime <= curTime))
				)
			)
		{
			deleteNote(i, iter);
		}
	}
}

void ControllerFallingMode::deleteNote(int trackIndex, vector<NoteObj>::iterator noteIter)
{
	noteIter->obj->removeFromParentAndCleanup(true);
	notesInTrack[trackIndex].erase(noteIter);
}

void ControllerFallingMode::changeHispeed()
{
	if ((changeSpeed < 0 && baseSpeed >= 0.5f) || (changeSpeed > 0 && baseSpeed <= 4.75f))
	{
		auto sy = 1 / (changeSpeed + baseSpeed);
		for (int i = 0; i < trackCount; i++)
		{
			for (vector<NoteObj>::iterator iter = notesInTrack[i].begin(); iter != notesInTrack[i].end(); iter++)
			{
				iter->obj->stopAllActionsByTag(1);
				if (iter->info.noteType == iter->info.SINGLE)
				{
					auto act = ScaleTo::create(.33f, 1.f, sy);
					act->setTag(1);
					iter->obj->runAction(ScaleTo::create(.33f, 1.f, sy));
				}
				else
				{
					auto lnheight = iter->obj->getContentSize().height;
					auto lnnewheight = lnheight - singleNoteHeight / baseSpeed + singleNoteHeight / (baseSpeed + changeSpeed);
					auto lnsy = lnnewheight / lnheight;
					auto act = ScaleTo::create(.33f, 1.f, lnsy);
					act->setTag(1);
					iter->obj->runAction(act);
				}
			}
		}
		baseSpeed += changeSpeed;
		changeSpeed = 0;
		auto fieldact = EaseSineOut::create(ScaleTo::create(.33f, 1.f, baseSpeed));
		fieldact->setTag(1);
		pNoteField->stopAllActionsByTag(1);
		pNoteField->runAction(fieldact);

		auto hslabel = Label::createWithTTF("Hi Speed  " + strtool::dou2str(baseSpeed), FONTA, 36);
		UI::setNodeAnch(hslabel, ANCH_M);
		hslabel->setPosition(pPanelFrame->getContentSize().width*.5f, pPanelFrame->getContentSize().height*.5f);
		hslabel->enableOutline(Color4B::BLACK, 2);
		hslabel->setScaleX(1.2f);
		pPanelFrame->addChild(hslabel);
		hslabel->runAction(
			Sequence::create(
				ScaleTo::create(.1f, 1),
				DelayTime::create(.2f),
				FadeTo::create(.2f, 0),
				RemoveSelf::create(),
				NULL
			)
		);
	}
}

void ControllerFallingMode::keyPress(int trackIndex)
{
	if (trackStatus[trackIndex].isPressed) trackStatus[trackIndex].pressTime += deltaTime;
	trackStatus[trackIndex].isPressed = true;
	checkHitRange(trackIndex, getGameTimeHighPrecision());
	char name[5];
	sprintf_s(name, 5, "tkl%d", trackIndex);
	auto trackLight = pPanelFrame->getChildByName(name);
	trackLight->runAction(
		ScaleTo::create(0.05f, trackWidth / trackLight->getContentSize().width, 1)
	);
	sprintf_s(name, 5, "key%d", trackIndex);
	auto keyObj = pPanelFrame->getChildByName(name);
	keyObj->runAction(
		TintTo::create(0.05f, Color3B(255, 150, 150))
	);
}

void ControllerFallingMode::keyPress(int trackIndex, float keyTime)
{
	if (trackStatus[trackIndex].isPressed) trackStatus[trackIndex].pressTime += deltaTime;
	else if (pNotemgr->trackNotes[trackIndex].size() > 0 && isSoundsLoaded)
	{
		if (notesInTrack[trackIndex].size() > 0)
			hitSoundPool.push_back(notesInTrack[trackIndex].begin()->info.bmsSoundIndex);
		else if (pNotemgr->trackNotes[trackIndex].size() > trackNoteIndex[trackIndex]) hitSoundPool.push_back(pNotemgr->trackNotes[trackIndex][trackNoteIndex[trackIndex]].bmsSoundIndex);
		else hitSoundPool.push_back(pNotemgr->trackNotes[trackIndex][trackNoteIndex[trackIndex] - 1].bmsSoundIndex);
	}
	trackStatus[trackIndex].isPressed = true;
	checkHitRange(trackIndex, keyTime);
}

void ControllerFallingMode::keyRelease(int trackIndex)
{
	trackStatus[trackIndex] = { false,false,0 };
}

void ControllerFallingMode::keyReleaseNextFrame(int trackIndex)
{
	trackStatus[trackIndex].isReleaseNextFrame = true;
}

void ControllerFallingMode::updateKeyEffect()
{
	for (int i = 0; i < trackCount; i++)
	{
		if (trackStatus[i].isPressed != trackLastStatus[i].isPressed)
		{
			if (trackStatus[i].isPressed)
			{
				pTrackLights[i]->stopAllActions();
				pKeyButtons[i]->runAction(
					//TintTo::create(0.05f, pUI->themeColor3B())
					FadeTo::create(.05f, 255)
				);
				pTrackLights[i]->runAction(
					ScaleTo::create(0.05f, (i == 0 ? trackWidthS : trackWidth) / pTrackLights[i]->getContentSize().width, 1)
				);
			}
			else
			{
				pKeyButtons[i]->runAction(
					Sequence::create(
						DelayTime::create(0.05f),
						//TintTo::create(0.1f, Color3B::WHITE),
						FadeTo::create(.05f, 150),
						NULL
					)
				);
				pTrackLights[i]->runAction(
					Sequence::create(
						DelayTime::create(0.05f),
						ScaleTo::create(0.1f, (i == 0 ? trackWidthS : trackWidth) / pTrackLights[i]->getContentSize().width, 0),
						NULL
					)
				);
				trackStatus[i].pressTime = 0;
			}
			trackLastStatus[i] = trackStatus[i];
		}
		if (trackStatus[i].isReleaseNextFrame) keyRelease(i);
	}
}

void ControllerFallingMode::showHitEffect()
{
	while (hitEffectPool.size() > 0)
	{
		int trackIndex = hitEffectPool[0].trackIndex;
		int hitRange = hitEffectPool[0].hitRange;
		hitEffectPool.erase(vector<HitEffect>::iterator(hitEffectPool.begin()));
		auto hitEffect = Sprite::create("image/GamePlay/FallingHitEffect.png");
		UI::setNodeAnch(hitEffect, Vec2(0.5f, 0.25f));
		hitEffect->setOpacity(180);
		hitEffect->setPosition(pTrackLights[trackIndex]->getPosition());
		hitEffect->setScale(2, 0);
		pPanelFrame->addChild(hitEffect);
		hitEffect->runAction(
			Sequence::create(
				Spawn::create(
					ScaleTo::create(0.20f, 0, 2),
					TintTo::create(0.10f, COLOR_HIT_RANGE[hitRange]),
					NULL
				),
				RemoveSelf::create(),
				NULL
			)
		);
		auto keypos = Vec2(pKeyButtons[trackIndex]->getPosition().x, 0);
		auto keydivepos = Vec2(keypos.x, -pKeyButtons[trackIndex]->getContentSize().height*.15f);
		auto keyaction = Sequence::create(
			MoveTo::create(0, keydivepos),
			MoveTo::create(.075f, keypos),
			NULL
		);
		keyaction->setTag(2);
		pKeyButtons[trackIndex]->stopAllActionsByTag(2);
		pKeyButtons[trackIndex]->runAction(keyaction);
	}
}

void ControllerFallingMode::checkHitRange(int trackIndex, float keyTime)
{
	if (!notesInTrack[trackIndex].empty())
	{
		vector<NoteObj>::iterator iter = notesInTrack[trackIndex].begin();
		if (!iter->hitting)
		{
			float hitOffset = keyTime - iter->info.startTime;
			float hitOffsetAbs = abs(hitOffset);
			int comboAddition = 0;
			if (hitOffsetAbs < OFF_PGREAT)
			{
				//hitSoundPool.push_back(0);
				curCombo++;
				totalAcc += 1;
				iter->hitLevel = HIT_PGREAT;
				hitEffectPool.push_back({ trackIndex,HIT_PGREAT });
				iter->hitting = true;
			}
			else if (hitOffsetAbs < OFF_GREAT)
			{
				//hitSoundPool.push_back(0);
				curCombo++;
				totalAcc += 3.f / 4;
				iter->hitLevel = HIT_GREAT;
				hitEffectPool.push_back({ trackIndex,HIT_GREAT });
				iter->hitting = true;
			}
			else if (hitOffsetAbs < OFF_GOOD)
			{
				//hitSoundPool.push_back(0);
				curCombo++;
				totalAcc += 1.f / 2;
				iter->hitLevel = HIT_GOOD;
				hitEffectPool.push_back({ trackIndex,HIT_GOOD });
				iter->hitting = true;
			}
			else if (hitOffsetAbs < OFF_POOR)
			{
				//hitSoundPool.push_back(0);
				curCombo = 0;
				totalAcc += 1.f / 4;
				iter->hitLevel = HIT_POOR;
				iter->hitting = true;
			}
			else if (hitOffsetAbs < OFF_BREAK)
			{
				curCombo = 0;
				iter->hitLevel = HIT_BREAK;
			}
			if (iter->hitLevel <= 5)
			{
				if (curCombo > maxCombo) maxCombo = curCombo;
				hitLevelCount[iter->hitLevel]++;
				totalHitCount++;
				notePlayed++;
				totalHitOffset += hitOffset;
				hitLevelPool.push_back({ iter->hitLevel,hitOffset });
				//int graphRowIndex = 1 + (OFF_POOR + hitOffset) / graphInterval;
				//if (graphRowIndex >= 0 && graphRowIndex < graphRows) graphOffsetCount[graphRows - 1 - graphRowIndex]++;
				//else if (graphRowIndex < 0) graphOffsetCount[graphRows - 1]++;
				//else if (graphRowIndex >= graphRows) graphOffsetCount[0]++;
			}
		}
	}
}

void ControllerFallingMode::checkNoteHit()
{
	for (int i = 0; i < trackCount; i++)
	{
		if (notesInTrack[i].size() > 0)
		{
			for (vector<NoteObj>::iterator iter = notesInTrack[i].begin();
				 iter != notesInTrack[i].end();
				 iter++)
			{
				if (iter->info.startTime > (curTime - OFF_BREAK) && !iter->hitting) break;
				else if ((iter->info.startTime < (curTime - OFF_BREAK)) && !iter->hitting)
				{
					curCombo = 0;
					iter->hitLevel = HIT_BREAK;
					iter->hitting = true;
					hitLevelCount[HIT_BREAK]++;
					notePlayed++;
					hitLevelPool.push_back({ HIT_BREAK,0 });
				}
				else if ((iter->info.startTime <= curTime) && (iter->info.noteType == iter->info.LONG) && (iter->hitLevel <= 3))
				{
					if (trackStatus[i].isPressed && (iter->info.endTime >= curTime))
					{
						iter->pressTime += deltaTime;
						//float comboAmount = pNotemgr->timingSegs[timeSegIndex].mspb / pNotemgr->beatDivisor;
						float comboAmount = pNotemgr->timingSegs[timeSegIndex].mspb / 4;
						for (; iter->pressTime > comboAmount; iter->pressTime -= comboAmount)
						{
							curCombo++;
							hitLevelCount[iter->hitLevel]++;
							hitEffectPool.push_back({ i,iter->hitLevel });
							hitLevelPool.push_back({ iter->hitLevel,0 });
						}
					}
					else if (!trackStatus[i].isPressed && ((iter->info.endTime - OFF_BREAK) > curTime))
					{
						curCombo = 0;
						totalAcc -= (3 - iter->hitLevel) / 3;
						iter->hitLevel = HIT_BREAK;
						hitLevelCount[HIT_BREAK]++;
						hitLevelPool.push_back({ HIT_BREAK,0 });
					}
				}
			}
		}
	}
}

void ControllerFallingMode::showHitLevel()
{
	while (hitLevelPool.size() > 0)
	{
		int hitLevel = hitLevelPool[0].hitLevel;
		float keyOffset = hitLevelPool[0].offset;
		hitLevelPool.erase(vector<HitLevel>::iterator(hitLevelPool.begin()));
		string strLevel, strCombo;
		char combo[8];
		_itoa(curCombo, combo, 10);
		pHitJudgment->setInfo(UIFallingModeHitJudgment::JUDGMENT(hitLevel), string(combo));
		if (SHOWKEYOFFSET)
		{
			char strOffset[8];
			if ((int)keyOffset != 0)
			{
				strLevel += keyOffset > 0 ? "\r\n+" : "\r\n";
				strLevel += string(_itoa((int)keyOffset, strOffset, 10));
				strLevel += " ms";
			}
			else strLevel += "\r\nNo offset";
		}
	}
}

void ControllerFallingMode::updateScore()
{
	avgAcc = totalAcc / notePlayed;
	avgHitOffset = totalHitOffset / totalHitCount;

	char strAbility[256];
	string sAbilityFormat = "MAX COMBO %d\r\n";
	sAbilityFormat += "ACCURACY %.2f %%\r\n";
	sAbilityFormat += "TOTAL OFFSET %.2f ms\r\n";
	sAbilityFormat += "DREAM %d\r\n";
	sAbilityFormat += "GREAT %d\r\n";
	sAbilityFormat += " GOOD %d\r\n";
	sAbilityFormat += " POOR %d\r\n";
	sAbilityFormat += "BREAK %d";
	sprintf(strAbility, sAbilityFormat.c_str(),
			maxCombo,
			avgAcc * 100,
			avgHitOffset,
			hitLevelCount[0],
			hitLevelCount[1],
			hitLevelCount[2],
			hitLevelCount[3],
			hitLevelCount[4]
	);
	pLabelAbility->setString(strAbility);
}

void ControllerFallingMode::updateOffsetGraph()
{
	for (int i = 0; i < graphRows; i++)
		pOffsetGraph[i]->setContentSize(Size(graphOffsetCount[i] * pLayerGraph->getContentSize().width*0.75 / totalNotesCount, graphRowHeight));
}

void ControllerFallingMode::autoPlay()
{
	if (!isAutoPlay)
	{
		return;
	}
	for (int i = 0; i < trackCount; i++)
	{
		for (auto note : notesInTrack[i])
		{
			if ((note.info.startTime <= (curTime + (type_file == 1 ? deltaTime*0.7 : 0))) && !note.hitting)
			{
				keyPress(i, curTime);
				if (note.info.noteType != note.info.LONG)
				{
					keyReleaseNextFrame(i);
				}
			}
			else if ((note.info.noteType == note.info.LONG) && (note.info.endTime <= curTime))
			{
				keyRelease(i);
			}
		}
	}
}

void ControllerFallingMode::checkNormalInput(CCKEYCODE keycode, bool isPressed)
{
	if (keycode == CCKEYCODE::KEY_TAB && isPressed) isAutoPlay = !isAutoPlay;
}

void ControllerFallingMode::checkHPInput()
{
	while (!exit)
	{
		try
		{
			HighPrecisionInput::HPKEYEVENT key = hInput->getkey();
			if (key.indexInKeyset >= 0)
			{
				if (key.keyevent) keyPress(key.indexInKeyset, key.time);
				else keyRelease(key.indexInKeyset);
			}
			this_thread::sleep_for(chrono::milliseconds(1));
		}
		catch (exception e) { return; }
	}
}

void ControllerFallingMode::playHitSound()
{
	while (!exit)
	{
		try
		{
			pAudioSystem->update();
			if (hitSoundPool.size() > 0)
			{
				//vector<int>::iterator iter = hitSoundPool.begin();
				for (auto i : hitSoundPool)
				{
					if (i >= 0 && i < pAudioSystem->getSfxPoolSize())
					{
						pAudioSystem->playSFX(i);
						//hitSoundPool.erase(iter);
					}
				}
				hitSoundPool.clear();
			}
			if (type_file == 2) playBgmChannel();
			this_thread::sleep_for(chrono::milliseconds(1));
		}
		catch (exception e) { return; }
	}
}

void ControllerFallingMode::playBgmChannel()
{
	//while (1)
	//{
	//	this_thread::sleep_for(chrono::milliseconds(1));
	//	pAudioSystem->update();
		//auto iter = pNotemgr->chlBgm.begin();
		//if (iter == pNotemgr->chlBgm.end()) return;
		//if (curTime < iter->time)
		//{
		//	continue;
		//}
		//else
	vector<NoteManager::CHANNELBGMOBJECT>::iterator bgm;
	while (pNotemgr->chlBgm.size() > 0)
	{
		bgm = pNotemgr->chlBgm.begin();
		if (bgm->time > curTime) break;
		pAudioSystem->playSFX(bgm->wavseq);
		CCLOG("Play %s, at %f, seq %d", bgm->filename.c_str(), bgm->time, bgm->wavseq);
		pNotemgr->chlBgm.erase(bgm);
	}
}


void ControllerFallingMode::loadSounds()
{
	pAudioSystem->releaseAllSFX();
	pAudioSystem->loadSoundFromFile(pNotemgr->musicFile, AudioSystem::SOUNDTYPE::LOAD_BGM, true);
	//pAudioSystem->loadSoundFromFile("sound/soft-hitnormal7.wav", AudioSystem::SOUNDTYPE::SFX, false);
	if (type_file == 2)
	{
		for (int i = 0; i < 1296; i++)
		{
			if (pNotemgr->wavdefs[i].seqno >= 0)
			{
				string sfxpath = pNotemgr->dataPath + pNotemgr->wavdefs[i].filename;
				if (_access(sfxpath.c_str(), 4) >= 0) pAudioSystem->loadSoundFromFile(sfxpath, AudioSystem::SOUNDTYPE::SFX);
				else
				{
					auto subindex = pNotemgr->wavdefs[i].filename.find_last_of('.');
					//string newname;
					if (pNotemgr->wavdefs[i].filename.substr(subindex, 4) == ".wav")
						sfxpath = pNotemgr->dataPath + pNotemgr->wavdefs[i].filename.substr(0, subindex) + ".ogg";
					else
						sfxpath = pNotemgr->dataPath + pNotemgr->wavdefs[i].filename.substr(0, subindex) + ".wav";
					auto isexist = _access(sfxpath.c_str(), 4);
					pAudioSystem->loadSoundFromFile(sfxpath, AudioSystem::SOUNDTYPE::SFX);
					//CCLOG((sfxpath + " Loaded.").c_str());
				}
			}
		}
	}
	else
		this_thread::sleep_for(chrono::seconds(3));
	isSoundsLoaded = true;
	isPaused = false;
}

void ControllerFallingMode::startThreads()
{
	isThreadStart = true;
	hTimer = new HighPrecisionTimer();
	hTimer->start();
	if (type_file == 2)
	{
		curTime = -2;
		//thread(&ControllerFallingMode::playBgmChannel, this).detach();
		thread(&ControllerFallingMode::setCurTimeHighPrecisionBMS, this).detach();
	}
	if (trackCount < 9)
	{
		hInput = new HighPrecisionInput(trackCount, pConfigKeys->getActiveKeySet(), hTimer, &curTime, type_file);
		hInput->start();
		thread(&ControllerFallingMode::checkHPInput, this).detach();
	}
	thread(&ControllerFallingMode::playHitSound, this).detach();
}


